package com.mr.service;


import com.b2c.common.utils.JsonUtils;
import com.b2c.common.utils.PageResult;
import com.fasterxml.jackson.core.type.TypeReference;
import com.mr.bo.SearchPageBo;
import com.mr.client.BrandClient;
import com.mr.client.CategoryClient;
import com.mr.client.GoodsClient;
import com.mr.client.SpecClient;
import com.mr.dao.GoodsRepository;
import com.mr.pojo.Goods;
import com.mr.service.pojo.*;
import com.mr.utils.HignLightUtil;
import com.mr.utils.SearchResult;
import com.sun.org.apache.xpath.internal.operations.Bool;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ClassName GoodsIndexService
 * @Description: TODO
 * @Author kun
 * @Date 2020/8/7
 * @Version V1.0
 **/
@Service
public class GoodsIndexService {

    @Autowired
    private BrandClient brandClient;

    //注入商品查询Client服务
    @Autowired
    private GoodsClient goodsClient;

    //注入分类查询Client服务
    @Autowired
    private CategoryClient cateGoryClient;
    //注入规格查询Client服务
    @Autowired
    private SpecClient specClient;

    @Autowired
    private GoodsRepository goodsRepository;
    //注入ElasticsearchTemplate进行高亮查询
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    public Goods getGoodsBySpu(Spu spu) {
        Long id = spu.getId();
        // 1、准备数据
        // 查询 spu下的   sku集合\=
        List<Sku> skus = goodsClient.querySkus(id);
        // 查询spu下的 大字段 商品详情等
        SpuDetail detail = goodsClient.queryDetail(id);
        // 商品分类（三级）
        List<String> names = cateGoryClient.queryCateNameByIds(
                Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        //查询spu所属分类的规格（用于搜索的字段）
        List<SpecParam> params = specClient.querySpecParam(null, spu.getCid3(), true,true);

        //价格集合
        List<Long> prices = new ArrayList<>();
        // 填充sku属性 sku用Map不用实体类 装入价格，图片等页面需要的元素
        List<Map<String,Object>> skuList = new ArrayList<>();
        for (Sku sku : skus) {
            prices.add(sku.getPrice());
            Map<String,Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            //取第一张图片即可
            map.put("image", StringUtils.isBlank(sku.getImages()) ? "" : sku.getImages().split(",")[0]);
            map.put("price", sku.getPrice());
            skuList.add(map);
        }

        // 处理规格参数
        Map<Long, String> genericMap = JsonUtils.parseMap(detail.getGenericSpec(), Long.class, String.class);
        Map<Long, List<String>> specialMap = JsonUtils.nativeRead(
                detail.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
                });

        Map<String, Object> specs = new HashMap<>();
        for (SpecParam param : params) {
            if(param.getGeneric()){
                // 通用参数
                String value = genericMap.get(param.getId());
                if(param.getNumeric()){
                    // 数值类型，需要存储一个分段例如5.0变成 4.8-5.2
                    //这样方法方便筛选，数字类型不用range区间查询，直接matchall就可以
                    value = this.buildSegment(value, param);

                }
                specs.put(param.getName(), value);
            }else{
                // 特有参数
                specs.put(param.getName(), specialMap.get(param.getId()));
            }
        }

        Goods goods = new Goods();

        goods.setBrandId(spu.getBrandId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime());
        goods.setId(id);
        goods.setSubTitle(spu.getSubTitle());
        // 搜索条件 拼接：标题、分类、品牌//
        //+ " " + StringUtils.join(names, " ")
        goods.setAll(spu.getTitle()+ StringUtils.join(names, " ") );
        goods.setPrice(prices);
        goods.setSkus(JsonUtils.serialize(skuList));
        goods.setSpecs(specs);

        return goods;
    }

    /**
     * 构建段  //"5.2"---》5.0-5.5英寸
     * @param value
     * @param p
     * @return
     */
    private String buildSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
                    result = segs[1] + p.getUnit() + "以下";
                }else{
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    /**
     * 查询商品分页for es
     * @return
     */
    public PageResult<Goods> queryGoodsPage(SearchPageBo goodsPageBo){
        //构造器
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        //搜索条件不为
        //, null
        if(StringUtils.isNotEmpty(goodsPageBo.getKey())){
            builder.withQuery(
                    QueryBuilders.boolQuery(  ).must(
                            QueryBuilders.matchQuery("all",goodsPageBo.getKey())
                    )
            );
        };

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        Map<String,String> filterMap = goodsPageBo.getFilter();
        if(filterMap != null && filterMap.size()!=0){
            Set<String> filterSet = filterMap.keySet();
            for(String key:filterSet){
                MatchQueryBuilder matchQueryBuilder=null;
                if(key.equals("cid3") || key.equals("brandId")){
                    matchQueryBuilder = QueryBuilders.matchQuery(key,filterMap.get(key));
                }else {
                    matchQueryBuilder = QueryBuilders.matchQuery("specs."+key+".keyword",filterMap.get(key));
                }
                boolQueryBuilder.must(matchQueryBuilder);
            }
            builder.withFilter(boolQueryBuilder);
        }

        //分页设置
        builder.withPageable(PageRequest.of(goodsPageBo.getPage(),goodsPageBo.getSize()));

        //结果过滤
        builder.withSourceFilter(new FetchSourceFilter(new String[]{"id","all","skus"},null));

        //填充聚合分类与品牌条件
        builder.addAggregation(AggregationBuilders.terms("cate_gro").field("cid3"));
        builder.addAggregation(AggregationBuilders.terms("brand_gro").field("brandId"));

        //查询结果
        Page<Goods>  goodsPage=goodsRepository.search(builder.build());

        //判断高亮
        if(StringUtils.isNotEmpty(goodsPageBo.getKey())){
            builder.withHighlightFields(new HighlightBuilder.Field("all").preTags("<font color='red'>").postTags("</font>"));
            Map<Long,String> map=HignLightUtil.getHignLigntMap(elasticsearchTemplate,builder.build(),Goods.class,"all");
            goodsPage.getContent().forEach(goods -> {
                //填充高亮
                goods.setAll(map.get(goods.getId()));
            });
        }

        //转换聚合结果
        AggregatedPage<Goods> page=
                (AggregatedPage<Goods>) goodsPage;
        //取出品牌聚合结果
        LongTerms brandTerms=(LongTerms)page.getAggregation("brand_gro");
        //获得buckets 分组数据集
        List<LongTerms.Bucket> brandTermsBuckets=brandTerms.getBuckets();
        //查询出品牌集合List
        List<BrandEntity> brandList=   brandTermsBuckets.stream().map(bucket -> {
            return  brandClient.queryBrandById(bucket.getKeyAsNumber().longValue());
        }).collect(Collectors.toList());

        //取出分类聚合结果
        LongTerms cateTerms=(LongTerms)page.getAggregation("cate_gro");
        //获得buckets 分组数据集
        List<LongTerms.Bucket> cateTermsBuckets=cateTerms.getBuckets();

        //取出分类id集合  maxDocCount：最大到文档数量 maxDocCateId是要展示分类id 根据id展示规格
        //我们计算聚合出的分类的 哪个分类下商品最多，记录下分类id
        final List<Long> maxDocCount=new ArrayList<>(1);
        maxDocCount.add(0l);
        final List<Long> maxDocCateId=new ArrayList<>(1);
        maxDocCateId.add(0l);
        //循环聚合出的，分类
        List<Long> cateIds=   cateTermsBuckets.stream().map(bucket -> {
            //比较商品数量，如果大于当前记录数量，则覆盖
            if(maxDocCount.get(0)<bucket.getDocCount()){
                maxDocCateId.set(0,bucket.getKeyAsNumber().longValue());
                maxDocCount.set(0,bucket.getDocCount());
            }
            return  bucket.getKeyAsNumber().longValue();
        }).collect(Collectors.toList());
        //查询出商品最多的分类id
        System.out.println("商品最多的分类"+maxDocCateId.get(0));

        //批量查询分类
        List<CategoryEntity> categoryList=cateGoryClient.queryCateLisByIds(cateIds);


        // 有分类id就聚合出该分类下的规格
        List<Map<String, Object>> specMap = null;
        if(maxDocCateId.get(0)!=0l){
            specMap = getSpecMap(categoryList.get(0).getId(), goodsPageBo);
        }

//        求总页数=总条数/每页条数，向上取整
        Long total=goodsPage.getTotalElements();
        Long totalPage=(long)Math.ceil(total.doubleValue()/goodsPageBo.getSize());

        //封装返回数据分页数据与聚合出的分类数据
        return  new SearchResult(goodsPage.getTotalElements(),totalPage,goodsPage.getContent(),categoryList,brandList,specMap);
    }

    /***
     * 聚合规格参数
     * @param cid 根据分类id查询参与筛选的规格
     * @param goodsPageBo 聚合规格时候需要加入条件搜索，
     * @return
     */
    private List<Map<String, Object>> getSpecMap(Long cid, SearchPageBo goodsPageBo) {

        // 根据分类id查询规格(设置为为前台搜索的)
        List<SpecParam> specParamList =
                this.specClient.querySpecParam(null, cid, true,true);

        // 创建集合，保存规格过滤条件
        List<Map<String, Object>> specsMap = new ArrayList<>();

       //设置查询条件，与商品查询一致，但本次主要是聚合筛选条件
       NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
      //设置查询条件
        if(StringUtils.isNotEmpty(goodsPageBo.getKey())){
            builder.withQuery(QueryBuilders.boolQuery().must(
                    QueryBuilders.matchQuery("all",goodsPageBo.getKey())
                    ));
            builder.withPageable(PageRequest.of(0,1));
        }


        // 聚合规格参数 需要带.keyword
        specParamList.forEach(p -> {
            String key = p.getName();
            builder.addAggregation(AggregationBuilders.terms(key).field("specs." + key + ".keyword"));

        });

        // 查询得到聚合结果
        AggregatedPage<Goods> page=
                (AggregatedPage<Goods>)this.goodsRepository.search(builder.build());
        // 循环规格参数集合，根据规格参数取出聚合结果
        specParamList.forEach(specParam ->  {
            Map<String, Object> spec = new HashMap<>();
            //规格名称 为key 值为规格值 例如 [key:"屏幕尺寸",values:[5英寸，6英寸]]
            spec.put("key", specParam.getName());
            //获得规格名称获得聚合数据
            StringTerms stringTerms=(StringTerms)page.getAggregation(specParam.getName());
            //获得buckets 分组数据集
            List<StringTerms.Bucket> list=stringTerms.getBuckets();
            //取出聚合但结果，转为String集合
            List<String> values=list.stream().map(bucket -> {
                return  bucket.getKeyAsString();
            }).collect(Collectors.toList());
            //放入到规格map
            spec.put("values", values);
            specsMap.add(spec);
        });

        return specsMap;

    }

    public void createIndex(Long id) throws IOException {

        Spu spu = this.goodsClient.querySpuById(id);
        // 构建商品
        Goods goods = this.getGoodsBySpu(spu);

        // 保存数据到索引库
        this.goodsRepository.save(goods);
    }

    public void deleteIndex(Long id) {
        this.goodsRepository.deleteById(id);
    }

}
